package org.testing.dbunit.extension;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.dbunit.DatabaseUnitException;
import org.dbunit.assertion.DbUnitAssert;
import org.dbunit.dataset.Column;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.operation.CompositeOperation;
import org.dbunit.operation.DatabaseOperation;
import org.testing.dbunit.extension.connection.DataBaseConnection;

import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;

public class DataSetExecutor {
    private static Comparator<IDataSet> NATURAL = new Comparator<IDataSet>() {
	@Override
	public int compare(IDataSet o1, IDataSet o2) {
	    return 1;
	}
    };

    private final DataBaseConnection manager;
    private List<IDataSet> cleanup = new ArrayList<IDataSet>();
    private DatabaseOperation sequenceResetter = DatabaseOperation.NONE;

    public DataSetExecutor(DataBaseConnection connection) {
	this.manager = connection;
    }

    public void addSequenceResetter(DatabaseOperation sequenceResetter) {
	this.sequenceResetter = sequenceResetter;
    }

    public void assertThis(IDataSet expectation) {
	try {
	    cleanup.add(expectation);
	    Map<String, Comparator<Object>> comparators = new HashMap<String, Comparator<Object>>();
	    assertThis(expectation, comparators);
	} catch (Exception e) {
	    throw new RuntimeException(e);
	}
    }

    public void assertThis(IDataSet expectation,
	    Map<String, Comparator<Object>> comparators)
	    throws DataSetException, SQLException, DatabaseUnitException {
	String[] tables = expectation.getTableNames();
	IDataSet actualSet = manager.getConnection()//
		.createDataSet(tables);
	for (String table : tables) {
	    ITable expected = expectation.getTable(table);
	    ITable actual = actualSet.getTable(table);
	    DbUnitAssert asserter = new AsserterWithComparators(comparators,
		    getColumns(expected, actual));
	    asserter.assertEquals(expected, actual);
	}
    }

    private Set<Column> getColumns(ITable expected, ITable actual) {
	try {
	    return Sets.union(
		    Sets.newHashSet(expected.getTableMetaData().getColumns()),
		    Sets.newHashSet(actual.getTableMetaData().getColumns()));
	} catch (DataSetException e) {
	    throw new RuntimeException(e);
	}
    }

    public void insert(IDataSet flat) {
	CompositeOperation operations = new CompositeOperation(
		sequenceResetter, DatabaseOperation.INSERT);
	execute(operations, flat);
    }

    public void cleanInsert(IDataSet flat) {
	CompositeOperation operations = new CompositeOperation(
		sequenceResetter, DatabaseOperation.CLEAN_INSERT);
	execute(operations, flat);
    }

    protected IDataSet add(IDataSet flat) {
	this.cleanup.add(flat);
	return flat;
    }

    private void execute(DatabaseOperation execute, IDataSet flat) {
	try {
	    add(flat);
	    execute.execute(manager.getConnection(), flat);
	} catch (Exception e) {
	    throw new RuntimeException(e);
	}
    }

    public void clean() {
	Collection<IDataSet> inReverseOrderToDelete = inReverseOrderToDelete();
	for (IDataSet clean : inReverseOrderToDelete) {
	    execute(DatabaseOperation.DELETE_ALL, clean);
	}
    }

    private Collection<IDataSet> inReverseOrderToDelete() {
	List<IDataSet> reversed = new ArrayList<IDataSet>();
	reversed.addAll(cleanup);
	Collections.sort(reversed, Ordering.from(NATURAL).reverse());
	return reversed;
    }
}